use std::collections::HashMap;
use regex::Regex;

#[allow(unused)]
mod unused {

use regex::Regex;
use std::collections::HashSet;

type Coord = (i64, i64, i64);
type Range = (i64, i64);

#[derive(Debug)]
struct Step {
    range_x: Range,
    range_y: Range,
    range_z: Range,
    turn_on: bool,
}

impl Step {
    fn operate(&self, core: &mut HashSet<Coord>) {
        let coords = self.coords();
        if self.turn_on {
            for coord in coords {
                core.insert(coord);
            }
        } else {
            for coord in coords {
                core.remove(&coord);
            }
        }
    }

    fn coords(&self) -> Vec<Coord> {
        let min_x = std::cmp::max(-50, self.range_x.0);
        let max_x = std::cmp::min(50, self.range_x.1);

        let min_y = std::cmp::max(-50, self.range_y.0);
        let max_y = std::cmp::min(50, self.range_y.1);
        
        let min_z = std::cmp::max(-50, self.range_z.0);
        let max_z = std::cmp::min(50, self.range_z.1);

        let mut coords = Vec::new();
        for x in min_x..=max_x {
            for y in min_y..=max_y {
                for z in min_z..=max_z {
                    coords.push((x, y, z));
                }
            }
        }
        coords
    }

}

impl From<&str> for Step {
    fn from(value: &str) -> Self {
        let re = Regex::new(r"-?\d+").unwrap();
        let numbers: Vec<i64> = re.find_iter(value)
                                  .map(|m| m.as_str())
                                  .map(|s| s.parse().unwrap())
                                  .collect();
        
        let turn_on = value.starts_with("on");

        Self {
            range_x: (numbers[0], numbers[1]),
            range_y: (numbers[2], numbers[3]),
            range_z: (numbers[4], numbers[5]),
            turn_on
        }
    }
}

fn parse_input() -> Vec<Step> {
    let input = std::fs::read_to_string("./day22/input.txt").unwrap();
    input.lines().map(|line| line.into()).collect()
}

#[allow(unused)]
fn part1(steps: &Vec<Step>) {
    let mut core = HashSet::new();
    steps.iter()
         .for_each(|s| s.operate(&mut core));
    println!("Part1: {}", core.len());
}
}

/////////////////////////////////////////////////////////////////////////////////////////////

#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]
struct Cube {
    min_x: i64,
    max_x: i64,
    min_y: i64,
    max_y: i64,
    min_z: i64,
    max_z: i64,
}

impl From<&str> for Cube {
    fn from(value: &str) -> Self {
        let re = Regex::new(r"-?\d+").unwrap();
        let numbers: Vec<i64> = re.find_iter(value)
                                  .map(|m| m.as_str())
                                  .map(|s| s.parse().unwrap())
                                  .collect();
        
        Self { 
            min_x: numbers[0], 
            max_x: numbers[1], 
            min_y: numbers[2], 
            max_y: numbers[3], 
            min_z: numbers[4], 
            max_z: numbers[5], 
        }
    }
}

impl Cube {
    fn new(min_x: i64, max_x: i64, min_y: i64, max_y: i64, min_z: i64, max_z: i64) -> Self {
        Self { min_x, max_x, min_y, max_y, min_z, max_z }
    }

    fn intersect(&self, other: &Cube) -> Option<Cube> {
        let min_x = std::cmp::max(self.min_x, other.min_x);
        let min_y = std::cmp::max(self.min_y, other.min_y);
        let min_z = std::cmp::max(self.min_z, other.min_z);
        let max_x = std::cmp::min(self.max_x, other.max_x);
        let max_y = std::cmp::min(self.max_y, other.max_y);
        let max_z = std::cmp::min(self.max_z, other.max_z);

        if min_x <= max_x && min_y <= max_y && min_z <= max_z {
            Some( Cube { min_x, max_x, min_y, max_y, min_z, max_z } )
        } else {
            None
        }
    }

    fn volume(&self) -> i64 {
        (self.max_x - self.min_x + 1) * 
        (self.max_y - self.min_y + 1) * 
        (self.max_z - self.min_z + 1)
    }
}

fn parse_input() -> Vec<(bool, Cube)> {
    let input = std::fs::read_to_string("./day22/input.txt").unwrap();
    input.lines().map(|line| {
        (line.starts_with("on"),  line.into())
    }).collect()
}

type Counter = HashMap<Cube, i64>;

fn operate(steps: &Vec<(bool, Cube)>, window: Option<Cube>) -> i64 {
    let mut counter = Counter::new();
    for (turn_on, cube) in steps {
        let cube = match &window {
            Some(w) => {
                match cube.intersect(w) {
                    None => continue,
                    Some(c) => c,
                }
            }
            None => *cube,
        };

        let mut new_counter = Counter::new();
        for (c, count) in counter.iter() {
            if let Some(inter) = cube.intersect(c) {
                let entry = new_counter.entry(inter).or_insert(0);
                *entry -= count;
            }
        }

        for (c, count) in new_counter {
            let entry = counter.entry(c).or_insert(0);
            *entry += count;
        } 

        if *turn_on {
            let entry = counter.entry(cube).or_insert(0);
            *entry += 1;
        }
    }

    counter.into_iter().map(|(c, count)| -> i64 {
        c.volume() * count
    }).sum()
}

#[allow(unused)]
fn part1(steps: &Vec<(bool, Cube)>) {
    let result = operate(steps, Some(Cube::new(-50, 50, -50, 50, -50, 50)));
    println!("Part1: {}", result);
}

#[allow(unused)]
fn part2(steps: &Vec<(bool, Cube)>) {
    let result = operate(steps, None);
    println!("Part2: {}", result);
}

fn main() {
    let steps = parse_input();
    part1(&steps);
    part2(&steps);
}
